package com.storyroad.utils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;

import com.storyroad.responseClasses.StoryView;
import com.storyroad.utils.Utilities;

/**
 * This class implements the search engine. Users can search for content in the system by specifying at least one of
 * the following: (1)location, (2)date and (3)keyword. If more than one of the previous three are used to do a search 
 * then the intersection of the individual search results are shown to the user. If the user specifies a location then all
 * story fragments who have the same location description are returned. If the user specifies a date then all story fragments
 * which are posted after the given date are returned. If the user specifies a keyword, then different searches are made in the
 * system according to the form of the keyword.
 * If the specified keyword is a tag with a semantic part (contains information about the tag in []) then first the whole tag
 * is used to search among the tags in the system (full tag search). Then the part in the bracket (with the brackets) is searched
 * among the tags (semantic search). The combination of these results are returned. 
 * If the specified keyword does not contain a semantic part then the usernames, story titles and tags (both the tag part
 * and the semantic part) in the system are searched and the combination of these search results are returned.
 *
 */
public class SearchEngine {

	private Connection conn;
	private String keywords;
	private String strDate;
	private String location;
	private Timestamp date;
	private ArrayList<Integer> locationResults;	// Results of location description based search
	private ArrayList<Integer> dateResults;	// Results of date based search
	private ArrayList<Integer> keywordResults;	// Results of keyword based search
	private Set<Integer> matchedContent;	// Used to store keyword search results before putting into a list IOT prevent duplicates.
	private PreparedStatement stmt;
	private ResultSet rs;
	
	public SearchEngine(Connection conn, String keywords, String date, String location){
		this.conn = conn;
		this.keywords = keywords;
		this.strDate = date;
		this.location = location;
		locationResults = null;
		keywordResults = null;
		dateResults = null;
		matchedContent = new HashSet<Integer>();
		if(this.strDate != null && this.strDate.length()>0){
			getTimestamp();
		}
	}
	
	/**
	 * Carries out location based search.
	 */
	public void locationSearch(){
		locationResults = new ArrayList<Integer>();
		try {
			stmt = conn.prepareStatement("SELECT s_id FROM " + TableNames.STORIES_TABLE + " WHERE " + "location = ?");
			stmt.setString(1, location);
			rs = stmt.executeQuery();
			while(rs.next()){
				locationResults.add(rs.getInt("s_id"));
			}
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
	 * Carries out date based search.
	 */
	public void dateSearch(){
		dateResults = new ArrayList<Integer>();
		try {
			stmt = conn.prepareStatement("SELECT s_id FROM " + TableNames.STORIES_TABLE + " s WHERE " + "DATEDIFF(?, s.date) <=0");
			stmt.setTimestamp(1, date);
			rs = stmt.executeQuery();
			while(rs.next()){
				dateResults.add(rs.getInt("s_id"));
			}
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
	 * Carries out a keyword based search.
	 */
	public void keywordSearch(){
		String regex = ".+\\[.*\\]$";
		String[] keyword = keywords.split(",");
		for(String word : keyword){
			if(word.matches(regex)){	//keyword contains []
				fullTagSearch(word);
				semanticSearch(word);
			}else{	//keyword does not contain []
				searchUsername(word);
				searchTitle(word);
				searchTagTable(word);
			}
		}
		keywordResults = new ArrayList<Integer>(matchedContent);
	}
	
	/**
	 * Takes the intersection of the three individual search result (if data is given by the user) to get
	 * the final search result.
	 * @return a list of {@link com.storyroad.responseClasses.StoryView} instances which store the search results
	 */
	public ArrayList<StoryView> generateResults(){
		boolean notNull = false;
		ArrayList<Integer> storyIDs = new ArrayList<Integer>();
		if(keywordResults != null){
			storyIDs.addAll(keywordResults);
			notNull = true;
		}
		if(locationResults != null){
			if(notNull){
				storyIDs.retainAll(locationResults);
			}else{
				storyIDs.addAll(locationResults);
				notNull = true;
			}
		}
		if(dateResults != null){
			if(notNull){
				storyIDs.retainAll(dateResults);
			}else{
				storyIDs.addAll(dateResults);
				notNull = true;
			}
		}

		ArrayList<StoryView> result = new ArrayList<StoryView>();

		for (int s_id : storyIDs) {
			StoryView storyView = Utilities.generateStoryView(conn,s_id);
			result.add(storyView);
		}
		return result;
	}
	
	/**
	 * Searches the usernames in the system using the given keyword
	 * @param keyword the keyword which will be used to search among the usernames
	 */
	private void searchUsername(String keyword){
		int u_id = Utilities.getUserId(conn, keyword);
		try {
			stmt = conn.prepareStatement("SELECT s_id FROM " + TableNames.STORIES_TABLE + " WHERE owner = ?");
			stmt.setInt(1, u_id);
			rs = stmt.executeQuery();
			while(rs.next()){
				matchedContent.add(rs.getInt("s_id"));
			}
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
	 * Searches the story fragment titles in the system using the given keyword. Keyword can match any word in the
	 * title.
	 * @param keyword the keyword which will be used to search among the story fragment titles
	 */
	private void searchTitle(String keyword){
		try {
			stmt = conn.prepareStatement("SELECT s_id FROM " + TableNames.STORIES_TABLE + " WHERE title REGEXP ?" );
			stmt.setString(1, "^" + keyword +" | " + keyword + "$| " + keyword + " |^" + keyword + "$");
			rs = stmt.executeQuery();
			while(rs.next()){
				matchedContent.add(rs.getInt("s_id"));
			}
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
	 * Searches the tags in the system using the given keyword. Keyword can match both the non-semantic and semantic
	 * part of a tag.
	 * @param keyword the keyword which will be used to search among the tags
	 */
	private void searchTagTable(String keyword){
		Set<String> semanticTags = new HashSet<String>();
		try {
			stmt = conn.prepareStatement("SELECT s_id,tag FROM " + TableNames.TAGS_TABLE + " WHERE tag REGEXP ?" );
			stmt.setString(1, "^" + keyword +" | " + keyword + " |\\[" + keyword + " | " + keyword + "\\]$");
			rs = stmt.executeQuery();
			while(rs.next()){
				matchedContent.add(rs.getInt("s_id"));
				semanticTags.add(rs.getString("tag"));
			}
			for(String semanticTag : semanticTags){
				semanticSearch(semanticTag);
			}
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
	 * Searches all the tags in the system using the given tag. The given tag contains a semantic part.
	 * @param tag tag to be searched
	 */
	private void fullTagSearch(String tag){
		try {
			stmt = conn.prepareStatement("SELECT s_id FROM " + TableNames.TAGS_TABLE + " WHERE tag = ?");
			stmt.setString(1, tag);
			rs = stmt.executeQuery();
			while(rs.next()){
				matchedContent.add(rs.getInt("s_id"));
			}
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
	 * Searches all the tags in the system using the semantic part of a tag (with the []).
	 * @param tag semantic part of a tag to be used in the search
	 */
	private void semanticSearch(String tag){
		int index = tag.indexOf("[");
		String semanticTag = tag.substring(index);
		if(semanticTag.length() > 2){
			try {
				stmt = conn.prepareStatement("SELECT s_id FROM " + TableNames.TAGS_TABLE + " WHERE tag LIKE ?");
				stmt.setString(1, "%" + semanticTag + "%");
				rs = stmt.executeQuery();
				while(rs.next()){
					matchedContent.add(rs.getInt("s_id"));
				}
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}		
		}
	}
	
	/**
	 * Creates a timestamp object from a given string
	 */
	private void getTimestamp(){
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
		java.util.Date today;
		try {
			today = sdf.parse(strDate);	
			date = new Timestamp(today.getTime());
		} catch (ParseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
